package com.tys.algorithm.beginner;

import java.util.Stack;

public class Code33_PartitionAndQuickSort {

    //交换数组中的数
    private static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    //用最后位置的数作为划分值：小于放左边，大于放右边
    public static void splitNum1(int[] arr) {
        int lessEqualR = -1; //小于等于区
        int index = 0; //当前位置
        int N = arr.length;
        while (index < N) {
            if (arr[index] <= arr[N - 1]) { //当前小于等于划分值
                //当前数和小于等于区的下一个数交换
                //当前数向前跳
                //小于等于区向前跳
                swap(arr, ++lessEqualR, index++);
            } else { //当前大于划分值
                //当前值向前跳
                index++;
            }
        }
    }

    //升级版：用数组最后的数做划分值，大于放左，等于放中间，小于放右，分三层
    public static void splitNum2(int[] arr) {
        int N = arr.length;
        int lessR = -1; //小于区起始位置
        int moreL = N - 1; //大于区起始位置，把划分值包住
        int index = 0;
        while (index < moreL) {
            if (arr[index] < arr[N - 1]) {//当前数小于划分值
                //当前数和小于区的下一个数交换
                //当前数向前跳
                //小于区向前跳
                swap(arr, ++lessR, index++);
            } else if (arr[index] > arr[N - 1]) { //当前数大于划分值
                //当前数和大于区的后一个数交换
                //大于区域向后跳
                //当前数不变
                swap(arr, --moreL, index);
            } else { //当前数等于划分值
                //当前数向前跳
                index++;
            }
        }
        //最后大于区的第一个值和划分值交换
        swap(arr, moreL, N - 1);
    }


    //快排
    //arr[L ... R] 范围上，拿arr[R]做划分值
    // L...R < = >
    //返回值表示等于区的左边界和右边界
    public static int[] partition(int[] arr, int L, int R) {
        int lesssR = L - 1; //小于区
        int moreL = R; //大于区
        int index = L; //当前数
        while (index < moreL) {
            if (arr[index] < arr[R]) {
                swap(arr, ++lesssR, index++);
            } else if (arr[index] > arr[R]) {
                swap(arr, --moreL, index);
            } else {
                index++;
            }
        }
        swap(arr, moreL, R);
        return new int[]{lesssR + 1, moreL};
    }

    //递归排序
    private static void process(int[] arr, int L, int R) {
        if (L >= R) {//不需要排序
            return;
        }
        int[] equalE = partition(arr, L, R); //获取等于区域范围
        process(arr, L, equalE[0] - 1); //左边递归
        process(arr, equalE[1] + 1, R); //右边递归
    }

    //递归快排
    public static void quickSort1(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        process(arr, 0, arr.length - 1);
    }


    //找到等于边界
    public static int[] netherlandsFlag(int[] arr, int L, int R) {
        if (L > R) { //越界
            return new int[]{-1, -1};
        }
        if (L == R) { //相等
            return new int[]{L, R};
        }

        int less = L - 1;
        int more = R;
        int index = L;
        while (index < more) {
            if (arr[index] == arr[R]) {
                index++;
            } else if (arr[index] < arr[R]) {
                swap(arr, index++, ++less);
            } else {
                swap(arr, index, --more);
            }
        }
        swap(arr, more, R);
        return new int[]{less + 1, more};
    }

    //快排随机交换
    public static void quickSort3(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        process3(arr, 0, arr.length - 1);
    }

    private static void process3(int[] arr, int L, int R) {
        if (L >= R) {
            return;
        }
        //随机交换，取划分值
        swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
        int[] equalArea = netherlandsFlag(arr, L, R);
        process3(arr, L, equalArea[0] - 1);
        process3(arr, equalArea[1], R);
    }


    //非递归快排，记录左边界和右边界
    public static class Job {
        public int L;
        public int R;

        public Job(int left, int right) {
            L = left;
            R = right;
        }
    }

    //非递归快排，借助栈实现
    public static void quickSort2(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        Stack<Job> stack = new Stack<>();
        stack.push(new Job(0, arr.length - 1)); //整个数组入栈
        while (!stack.isEmpty()) {
            Job cur = stack.pop();
            int[] equals = partition(arr, cur.L, cur.R); //找到等于区域
            if (equals[0] > cur.L) { //有小于区
                stack.push(new Job(cur.L, equals[0] - 1)); //小于区入栈
            }
            if (equals[1] < cur.R) {//有大于区
                stack.push(new Job(equals[1] + 1, cur.R)); //大于区入栈
            }
        }
    }


    //产生随机数组
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    //拷贝数组
    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }

    //对比数组是否相同
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    //打印数组
    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[] arr = {7, 1, 3, 5, 4, 5, 1, 4, 2, 4, 2, 4};
//        splitNum1(arr);
        splitNum2(arr);
        printArray(arr);

        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        boolean succeed = true;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            int[] arr3 = copyArray(arr1);
            quickSort1(arr1);
            quickSort2(arr2);
            quickSort3(arr3);
            if (!isEqual(arr1, arr3) || !isEqual(arr1, arr2)) {
                System.out.println("失败");
                succeed = false;
                break;
            }
        }
        System.out.println("测试结束");
        System.out.println(succeed ? "成功" : "失败");
    }
}
